2026_daft_punk_veridis_quo.py

#

SPDX-FileCopyrightText: 2026 Marina Hinojal Vicente & Yu-Hsiang Hu SPDX-FileCopyrightText: 2026 AlICe laboratory https://alicelab.be

SPDX-License-Identifier: GPL-3.0-or-later

import math
import os
import random

import bmesh
import bpy

os.system("cls")
#
def clean():
    objects_to_keep = ["Camera__Iso"]
    for obj in bpy.data.objects:
        if obj.name in objects_to_keep:
            continue

        bpy.data.objects.remove(obj, do_unlink=True)

    for mesh in bpy.data.meshes:
        if not mesh.users:
            bpy.data.meshes.remove(mesh)


clean()

total_faces = range(289)
faces_of_flute = [
    28,
    29,
    30,
    31,
    32,
    45,
    46,
    47,
    48,
    49,
    77,
    92,
    93,
    94,
    95,
    109,
    110,
    111,
    112,
    126,
    127,
    129,
    175,
    176,
    177,
    178,
    192,
    193,
    194,
    195,
    207,
    222,
    223,
    224,
    225,
    239,
    240,
    241,
    242,
    256,
    257,
    259,
]
opposite_faces = [i for i in total_faces if i not in faces_of_flute]
#
def Delete_faces(_Mesh, faces_to_delete):
    myObj = bpy.data.objects[_Mesh]
    myObjGeom = myObj.data
    myBmesh = bmesh.new()
    myBmesh.from_mesh(myObjGeom)
#

    myBmesh.faces.ensure_lookup_table()

    max_faces = len(myBmesh.faces)

    _Faces = [myBmesh.faces[i] for i in faces_to_delete]
    bmesh.ops.delete(myBmesh, geom=_Faces, context="FACES")
#

edge clean up

    edges_to_remove = [e for e in myBmesh.edges if len(e.link_faces) == 0]
    bmesh.ops.delete(myBmesh, geom=edges_to_remove, context="EDGES")
#

    myBmesh.to_mesh(myObjGeom)
    myObj.data.update()
    myBmesh.free()
#
def generateGrid(_GridName, Obj_num, z_location):
    bpy.ops.mesh.primitive_grid_add(
        x_subdivisions=17,
        y_subdivisions=17,
        location=(0, 0, z_location),
        size=10,
    )

    bpy.context.object.name = _GridName
    Obj_num = bpy.data.objects[_GridName]
#
def _Modifiers_Smooth(_Mesh):
    myObj = bpy.data.objects[_Mesh]
    smooth_it = myObj.modifiers.new(name="smooth_it", type="SUBSURF")
    smooth_it.levels = 2
    smooth_it.boundary_smooth = "PRESERVE_CORNERS"

    bpy.ops.object.modifier_apply(modifier=smooth_it.name)
#
def _Modifiers_Bevel(_Mesh, _width, _seg):
    myObj = bpy.data.objects[_Mesh]
    bevel_it = myObj.modifiers.new(name="bevel_it", type="BEVEL")
    bevel_it.width = _width
    bevel_it.segments = _seg
#
def Extrude_Z(_Mesh, _min, _max):
    myObj = bpy.data.objects[_Mesh]
#

activate target obj

    bpy.context.view_layer.objects.active = myObj
#

enter edit mode

    bpy.ops.object.mode_set(mode="EDIT")
#

select all surfaces

    bpy.ops.mesh.select_mode(type="FACE")
    bpy.ops.mesh.select_all(action="SELECT")
#

extrude via z axis

    _thickness = random.uniform(_min, _max)
    bpy.ops.mesh.extrude_region_move(
        TRANSFORM_OT_translate={"value": (0, 0, _thickness)}
    )
#

object mode

    bpy.ops.object.mode_set(mode="OBJECT")
#
def Remodel_Proportional(_Mesh, _CenterIndices, _radius, _min_h, _max_h):
    myObj = bpy.data.objects[_Mesh]

    for v_index in _CenterIndices:
#

to have the x,y coordinate of every verts in CenterIndices

        center_vert = myObj.data.vertices[v_index]
        center_x = center_vert.co.x
        center_y = center_vert.co.y
#

define a variation to have a random height

        target_wave_height = random.uniform(_min_h, _max_h)
#

create the wavy surface

        for v in myObj.data.vertices:
#

Pythagoras’ theorem(a^2 + b^2 = c^2)

            dist = math.sqrt((v.co.x - center_x) ** 2 + (v.co.y - center_y) ** 2)
#

useing _radius to define the scope moving when dist=_radius, dont’t move when dist=0, move the most

            if dist < _radius:
                falloff = (math.cos(math.pi * (dist / _radius)) + 1.0) / 2.0

                v.co.z += target_wave_height * falloff
#
def Move_Independently(_Mesh, min_z, max_z):

    myObj = bpy.data.objects[_Mesh]

    bpy.ops.object.mode_set(mode="EDIT")
    bpy.ops.mesh.separate(type="LOOSE")  # separate independent mesh
    bpy.ops.object.mode_set(mode="OBJECT")
#

move separated parts independently

    separated_parts = bpy.context.selected_objects
    for obj in separated_parts:
        z_offset = random.uniform(min_z, max_z)
        obj.location.z += z_offset
#

join the mesh again

    bpy.context.view_layer.objects.active = separated_parts[0]
    bpy.ops.object.join()
    bpy.context.active_object.name = _Mesh
#
def _ScaleUp(_Mesh, min_scale, max_scale):

    original_obj = bpy.data.objects.get(_Mesh)
    original_obj.select_set(True)
    bpy.context.view_layer.objects.active = original_obj
#

duplicate

    bpy.ops.object.duplicate()
    duplicated_obj = bpy.context.active_object
#

separate into independent mesh

    bpy.ops.object.mode_set(mode="EDIT")
    bpy.ops.mesh.select_all(action="SELECT")
    bpy.ops.mesh.separate(type="LOOSE")
    bpy.ops.object.mode_set(mode="OBJECT")
#

reset the origin of each geometry

    bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center="MEDIAN")

    separated_parts = bpy.context.selected_objects
#

scale up each part

    for i in range(len(separated_parts)):
        obj = separated_parts[i]
        obj.name = f"{_Mesh}_scaleup_part_{i + 1}"
        s = random.uniform(min_scale, max_scale)
        obj.scale = (s, s, s)
#
def Clean_Mesh(_Mesh):
    myObj = bpy.data.objects.get(_Mesh)
    bpy.context.view_layer.objects.active = myObj
    bpy.ops.object.mode_set(mode="EDIT")
#

remove overlapped vertices

    bpy.ops.mesh.select_all(action="SELECT")
    bpy.ops.mesh.remove_doubles(threshold=0.001)
#

delete interior Faces

    bpy.ops.mesh.select_all(action="DESELECT")
    bpy.ops.mesh.select_interior_faces()
    bpy.ops.mesh.delete(type="FACE")
#

reset normals

    bpy.ops.mesh.select_all(action="SELECT")
    bpy.ops.mesh.normals_make_consistent(inside=False)

    bpy.ops.object.mode_set(mode="OBJECT")
#
def boolean_Difference(operand_A, operand_B):
    Boolean_boxes = operand_A.modifiers.new(name="my_Boolean", type="BOOLEAN")
    Boolean_boxes.operation = "DIFFERENCE"
    Boolean_boxes.object = operand_B
    Boolean_boxes.solver = "EXACT"
#
def boolean_Intersect(operand_A, operand_B):
    Boolean_boxes = operand_A.modifiers.new(name="my_Boolean", type="BOOLEAN")
    Boolean_boxes.operation = "INTERSECT"
    Boolean_boxes.object = operand_B
    Boolean_boxes.solver = "EXACT"
#

Start of the Keyboard -----------------------------------------------------------

FacesToMove_Top = [0, 1, 2, 17, 18, 19, 34, 35, 36]
FacesToMove_Bottom = [0, 14, 20]

CentersToMove_Top = [
    0,
    314,
    17,
]

CentersToMove_Bottom = [
    306,
    9,
    323,
]

generateGrid("keyboard_top", "kb_1", 4.5)
Remodel_Proportional("keyboard_top", CentersToMove_Top, 6.0, 2, 4)
Remodel_Proportional("keyboard_top", CentersToMove_Bottom, 6.0, -3, -3)
_Modifiers_Smooth("keyboard_top")

generateGrid("keyboard_bottom", "kb_2", 4.3)
Remodel_Proportional("keyboard_bottom", CentersToMove_Bottom, 6.0, -3, -6)
Remodel_Proportional("keyboard_bottom", CentersToMove_Top, 6.0, 1, 2)
_Modifiers_Smooth("keyboard_bottom")
#

----- Bridge the edge

obj_top = bpy.data.objects["keyboard_top"]
obj_bottom = bpy.data.objects["keyboard_bottom"]
#

select the two surfaces

bpy.ops.object.select_all(action="DESELECT")
obj_top.select_set(True)
obj_bottom.select_set(True)
#

joing the two surface before bridging

bpy.context.view_layer.objects.active = obj_top
bpy.ops.object.join()
#

change the name

obj_top.name = "Keyboard_Solid"
#

Enter Edit Mode

bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_mode(type="EDGE")
bpy.ops.mesh.select_all(action="DESELECT")
#

Select Non-manifold edges

bpy.ops.mesh.select_non_manifold()
#

Bridge them

bpy.ops.mesh.bridge_edge_loops()
#

Recalculate Normals

bpy.ops.mesh.normals_make_consistent(inside=False)
#

Object Mode

bpy.ops.object.mode_set(mode="OBJECT")
#

----- Carve the keyboard

generateGrid("Void", "obj_6", -5)
Delete_faces("Void", opposite_faces)
Extrude_Z("Void", 20, 20)
_Modifiers_Bevel("Void", 0.2, 20)
_ScaleUp("Void", 1.0, 1.2)


Clean_Mesh("Keyboard_Solid")  # To prevent from non-manifold after boolean
boolean_Difference(
    bpy.data.objects["Keyboard_Solid"], bpy.data.objects["Void_scaleup_part_1"]
)
boolean_Difference(
    bpy.data.objects["Keyboard_Solid"], bpy.data.objects["Void_scaleup_part_2"]
)
boolean_Difference(
    bpy.data.objects["Keyboard_Solid"], bpy.data.objects["Void_scaleup_part_3"]
)
boolean_Difference(
    bpy.data.objects["Keyboard_Solid"], bpy.data.objects["Void_scaleup_part_4"]
)

bpy.data.objects["Void"].hide_set(True)
for i in range(4):
    bpy.data.objects[f"Void_scaleup_part_{i + 1}"].hide_set(True)


bpy.ops.mesh.primitive_cube_add(size=1, location=(0, 0, 5), scale=(11, 11, 10))
boundary_obj = bpy.context.active_object
boundary_obj.name = "boundary"
boundary_obj.data.name = "boundary_mesh"
boolean_Intersect(bpy.data.objects["Keyboard_Solid"], bpy.data.objects["boundary"])
bpy.data.objects["boundary"].hide_set(True)
#

End of the Keyboard -------------------------------------------------------------

#

Start of the Flute --------------------------------------------------------------

generateGrid("Flute", "obj_4", 7)
Remodel_Proportional("Flute", CentersToMove_Top, 6.0, 1, 5)
Delete_faces("Flute", opposite_faces)
_Modifiers_Smooth("Flute")
Move_Independently("Flute", -8, -2)


Extrude_Z("Flute", 2.5, 5.5)
_Modifiers_Bevel("Flute", 0.2, 20)
#

— Add red color

RedColor = bpy.data.materials.new("Red")
RedColor.diffuse_color = (1.0, 0.0, 0.0, 1.0)  # (R, G, B, Transparency)
bpy.data.objects["Flute"].data.materials.append(RedColor)
#

End of the Flute ----------------------------------------------------------------

#

Start of the Beat ---------------------------------------------------------------

#
def _beat_a(_pos, _dim, _name):
    bpy.ops.mesh.primitive_cube_add(size=1, location=_pos, scale=_dim)
    bpy.context.object.name = _name
    bpy.context.object.data.name = _name + "_mesh"
#
def _beat_b(_pos, _dim, _name):
    bpy.ops.mesh.primitive_cube_add(size=1, location=_pos, scale=_dim)
    obj_beat = bpy.context.active_object
    obj_beat.name = _name
    obj_beat.data.name = _name + "_mesh"

    bpy.ops.object.duplicate(linked=False)
    obj_copy = bpy.context.object
    obj_copy.name = _name + "_copy"
    obj_copy.rotation_euler[2] = math.radians(90)

    myObj = bpy.data.objects[_name]

    obj_beat.select_set(True)
    obj_copy.select_set(True)
    bpy.context.view_layer.objects.active = obj_beat
    bpy.ops.object.join()
#

define the start point and step, and use 10/34 to make sure the center of every beat is in grid

start_val = (10 / 34) * 5 - 5  # '-5' because the grid center is (0,0)
step_val = (10 / 34) * 8
#

Add a factor to control the height of the beat beat(0,0) and beat(3,3) are the biggest, decreasing toward center

max_abs_sum = abs(start_val * 2)

for rowY in range(4):
    for rowX in range(4):
#

calculate the real xy location

        realX = start_val + (rowX) * step_val
        realY = start_val + (rowY) * step_val

        min_height = random.uniform(10.0, 10.0)
        max_height = random.uniform(10.0, 10.0)
#

factor based on abs(realX + realY)

        factor = abs(realX + realY) / max_abs_sum
#

keep a minimum height and add a value based on the factor

        base_height = min_height + (max_height - min_height) * factor
#

To have a slightly variation

        final_height = random.uniform(base_height - 0.5, base_height + 0.5)

        if (rowX + rowY) % 2 == 0:
            rSize_a = random.uniform(0.4, 0.5)
            name_a = "Beat_a"
            _beat_a(
                (realX, realY, final_height / 2),
                (rSize_a, rSize_a, final_height),
                name_a,
            )
        else:
            name_b = "Beat_b"
            rSize_b = random.uniform(0.4, 0.5)
            _beat_b(
                (realX, realY, final_height / 2),
                (rSize_b, rSize_b / 5, final_height),
                name_b,
            )
#

End of the Beat ----------------------------------------------------------------

#

Start of the Tension and Atmoshere -----------------------------------------------

#
def create_line_between(pA, pB, _rmin, _rmax):
    dx = pB[0] - pA[0]
    dy = pB[1] - pA[1]
    dz = pB[2] - pA[2]
    dist = math.sqrt(dx**2 + dy**2 + dz**2)

    mid_x = (pA[0] + pB[0]) / 2.0
    mid_y = (pA[1] + pB[1]) / 2.0
    mid_z = (pA[2] + pB[2]) / 2.0

    _radius = random.uniform(_rmin, _rmax)
    bpy.ops.mesh.primitive_cylinder_add(
        radius=_radius,
        depth=dist,
        location=(mid_x, mid_y, mid_z),
    )

    phi = math.atan2(dy, dx)
    theta = math.acos(dz / dist)

    bpy.context.object.rotation_euler[1] = theta
    bpy.context.object.rotation_euler[2] = phi


coords_up = []
coords_down = []
#

bubbles above the plane

for _ in range(15):
    pos_x = random.uniform(-6.0, 6.0)
    pos_y = random.uniform(-6.0, 6.0)
    pos_z = random.uniform(5.0, 12.0)
    _r_radius = random.uniform(0.25, 1.0)

    coords_up.append((pos_x, pos_y, pos_z))
    bpy.ops.mesh.primitive_uv_sphere_add(
        radius=1,
        location=(pos_x, pos_y, pos_z),
        scale=(_r_radius, _r_radius, _r_radius),
    )
#

bubbles below the plane

for _ in range(10):
    pos_x = random.uniform(-6.0, 6.0)
    pos_y = random.uniform(-6.0, 6.0)
    pos_z = random.uniform(-2.0, 5.0)
    _r_radius = random.uniform(0.25, 1.0)

    coords_down.append((pos_x, pos_y, pos_z))
    bpy.ops.mesh.primitive_uv_sphere_add(
        radius=1,
        location=(pos_x, pos_y, pos_z),
        scale=(_r_radius, _r_radius, _r_radius),
    )
#

change the sequence of the list to make it more random

random.shuffle(coords_up)
random.shuffle(coords_down)
#

connect the bubbles three times

for i in range(3):
#

to have 2 bubbles above,i and i+1

    p1_up = coords_up[i * 2]
    p2_up = coords_up[i * 2 + 1]
#

to have 2 bubbles below

    p3_down = coords_down[i]
#

connect these points

    create_line_between(p1_up, p2_up, 0.075, 0.1)
    create_line_between(p2_up, p3_down, 0.075, 0.1)
    create_line_between(p3_down, p1_up, 0.075, 0.1)
#

End of the Tension and Atmoshere ------------------------------------------------